/*
 * Copyright 2010 Alexey Romensky
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

/*
 * User: Alexey Romensky
 * Date: 20.02.2010
 * Time: 21:00:28
 */
package com.googlecode.gwt.logging.api.rebind;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.ext.*;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import com.googlecode.gwt.logging.api.client.LogData;
import com.googlecode.gwt.logging.api.client.LogWriter;
import com.googlecode.gwt.logging.api.client.LogWriterFactory;
import org.slf4j.Marker;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

import static com.google.gwt.core.ext.TreeLogger.ERROR;

public class LogWriterFactoryGenerator extends Generator {

    private static final Class<LogWriterFactory> SOURCE_TYPE = LogWriterFactory.class;

    private static final String PACKAGE_NAME = SOURCE_TYPE.getPackage().getName();

    private static final String CLASS_NAME = SOURCE_TYPE.getSimpleName() + "Impl";

    private static final String PROPERTY_LOGGERS = "loggers";

    private static final String[] BOOL_METHOD_NAMES = new String[]{"isFatalEnabled", "isErrorEnabled", "isWarnEnabled",
            "isInfoEnabled", "isDebugEnabled", "isTraceEnabled"};

    private static final String[] LOG_METHOD_NAMES = new String[]{"fatal", "error", "warn", "info", "debug", "trace"};

    @Override
    public String generate(TreeLogger logger, GeneratorContext context, String typeName)
            throws UnableToCompleteException {

        final ClassSourceFileComposerFactory composerFactory =
                new ClassSourceFileComposerFactory(PACKAGE_NAME, CLASS_NAME);

        final PrintWriter pw = context.tryCreate(logger, PACKAGE_NAME, CLASS_NAME);
        if (pw != null) {

            final List<String> values = getPropertyValues(logger, context);
            final List<String> qualifiedSourceNames = getQualifiedSourceNames(logger, context, values);

            composerFactory.addImplementedInterface(SOURCE_TYPE.getName());
            composerFactory.addImport(LogWriterFactory.class.getName());
            composerFactory.addImport(LogWriter.class.getName());
            composerFactory.addImport(GWT.class.getName());
            composerFactory.addImport(Marker.class.getName());
            composerFactory.addImport(LogData.class.getName());

            final SourceWriter sw = composerFactory.createSourceWriter(context, pw);

            appendFactoryDefinitions(sw, qualifiedSourceNames);

            sw.println();
            sw.println("@Override");
            sw.println("public LogWriter getLogWriter(final String name) {");
            sw.indent();
            sw.println();
            sw.println("return new LogWriter() {");
            sw.indent();

            appendLoggerInstantionations(sw, qualifiedSourceNames.size());

            appendBoolMethods(sw, qualifiedSourceNames.size());

            appendLogMethods(sw, qualifiedSourceNames.size());

            sw.outdent();
            sw.println("};");
            sw.outdent();
            sw.println("}");

            sw.commit(logger);
        }

        return composerFactory.getCreatedClassName();
    }

    private void appendLogMethods(SourceWriter sw, int count) {

        for (String logMethodName : LOG_METHOD_NAMES) {

            sw.println();
            sw.println("@Override");
            sw.print("public void ");
            sw.print(logMethodName);
            sw.println("(LogData data) {");
            sw.indent();

            for (int i = 0; i < count; i++) {

                sw.print("writer");
                sw.print(String.valueOf(i));
                sw.print(".");
                sw.print(logMethodName);
                sw.println("(data);");
            }

            sw.outdent();
            sw.println("}");
        }
    }

    private void appendBoolMethods(SourceWriter sw, int count) {

        for (String boolMethodName : BOOL_METHOD_NAMES) {

            sw.println();
            sw.println("@Override");
            sw.print("public boolean ");
            sw.print(boolMethodName);
            sw.println("(Marker marker) {");
            sw.indent();
            sw.print("return ");

            if (count == 0) {
                sw.print("false");
            } else {

                for (int i = 0; i < count; i++) {

                    if (i > 0) {
                        sw.print(" | ");
                    }

                    sw.print("writer");
                    sw.print(String.valueOf(i));
                    sw.print(".");
                    sw.print(boolMethodName);
                    sw.print("(marker)");
                }
            }

            sw.println(";");
            sw.outdent();
            sw.println("}");
        }
    }

    private void appendLoggerInstantionations(SourceWriter sw, int count) {

        for (int i = 0; i < count; i++) {
            sw.println();
            sw.print("private final LogWriter writer");
            sw.print(String.valueOf(i));
            sw.print(" = FACTORY");
            sw.print(String.valueOf(i));
            sw.println(".getLogWriter(name);");
        }
    }

    private void appendFactoryDefinitions(SourceWriter sw, List<String> qualifiedSourceNames) {

        int i = 0;
        for (final String name : qualifiedSourceNames) {
            sw.println();
            sw.print("private static final LogWriterFactory FACTORY");
            sw.print(String.valueOf(i));
            sw.print(" = GWT.create(");
            sw.print(name);
            sw.println(".class);");
            i++;
        }
    }

    private List<String> getQualifiedSourceNames(TreeLogger logger, GeneratorContext context, List<String> values)
            throws UnableToCompleteException {

        final List<String> qualifiedSourceNames = new ArrayList<String>(values.size());

        final TypeOracle typeOracle = context.getTypeOracle();

        boolean failed = false;

        for (final String value : values) {

            try {
                final JClassType userType = typeOracle.getType(value);
                final String qualifiedSourceName = userType.getQualifiedSourceName();
                qualifiedSourceNames.add(qualifiedSourceName);
            } catch (NotFoundException e) {
                logger.log(ERROR, "unable to find logger class " + value, e);
                failed = true;
            }
        }

        if (failed) {
            throw new UnableToCompleteException();
        }

        return qualifiedSourceNames;
    }

    private List<String> getPropertyValues(TreeLogger logger, GeneratorContext context)
            throws UnableToCompleteException {

        final PropertyOracle propertyOracle = context.getPropertyOracle();
        final ConfigurationProperty property;

        try {
            property = propertyOracle.getConfigurationProperty(PROPERTY_LOGGERS);
        } catch (BadPropertyValueException e) {
            logger.log(ERROR, "unable to find value for configuration property \"" + PROPERTY_LOGGERS + "\"", e);
            throw new UnableToCompleteException();
        }

        return property.getValues();
    }
}
